package ph.com.gs3.formalistics.service.services;

import java.util.ArrayList;
import java.util.List;

import ph.com.gs3.formalistics.global.utilities.logging.FLLogger;
import ph.com.gs3.formalistics.global.utilities.services.ServiceEventNotifierUtility;
import ph.com.gs3.formalistics.model.communicators.DocumentCommunicatorModule;
import ph.com.gs3.formalistics.model.communicators.HttpCommunicator;
import ph.com.gs3.formalistics.model.dao.DocumentsDAO;
import ph.com.gs3.formalistics.model.dao.FormsDAO;
import ph.com.gs3.formalistics.model.dao.OutgoingDocumentsDAO;
import ph.com.gs3.formalistics.model.dao.OutgoingStarMarksDAO;
import ph.com.gs3.formalistics.model.dao.UserAccountsDAO;
import ph.com.gs3.formalistics.model.dao.UserAccountsDAO.IncompleteUserValuesException;
import ph.com.gs3.formalistics.model.valueobjects.business.User;
import ph.com.gs3.formalistics.model.valueobjects.business.User.UserParseException;
import ph.com.gs3.formalistics.model.valueobjects.business.document.Document;
import ph.com.gs3.formalistics.model.valueobjects.business.document.DocumentStub;
import ph.com.gs3.formalistics.model.valueobjects.business.form.FormStub;
import ph.com.gs3.formalistics.presenter.MainActivity;
import ph.com.gs3.formalistics.service.managers.DocumentManager;
import ph.com.gs3.formalistics.service.managers.DocumentManager.DocumentDownloadFailedException;
import ph.com.gs3.formalistics.service.managers.DocumentManager.DocumentUpdateFailedException;
import ph.com.gs3.formalistics.service.managers.DocumentManager.GetDocumentUpdatesResult;
import android.app.IntentService;
import android.content.Intent;
import android.os.Bundle;

/**
 * Back end service.
 * 
 * Manages Updating of documents using the already downloaded forms in the database.
 * Updating will start as soon as startService is invoked.
 * 
 * In the current implementation, all document updates from each form will be consolidated
 * and sorted by update date. After sorting, the service will then proceed to download &
 * update documents one by one.
 * 
 * The intent used to start this service must include the current server as extra using
 * the key EXTRA_SERVER and the current user's company id using the key
 * EXTRA_USER_COMPANY_ID.
 * 
 * This service has 4 broadcast actions, ACTION_ON_UPDATE_STARTED,
 * ACTION_ON_DOCUMENT_UPDATED, ACTION_ON_DOCUMENT_UPDATE_ERROR, and
 * ACTION_ON_DOCUMENT_UPDATE_ERROR.
 * 
 * @author Ervinne Sodusta
 */
public class DocumentsUpdateService extends IntentService {

	// {{ Constants

	public static final String TAG = DocumentsUpdateService.class.getSimpleName();

	public static final String NAME = FormsUpdateService.class.getName();

	// broadcast actions
	public static final String ACTION_ON_UPDATE_STARTED = NAME + "_update_started";
	public static final String ACTION_ON_DOCUMENT_UPDATED = NAME + "_document_updated";
	public static final String ACTION_ON_DOCUMENT_UPDATE_ERROR = NAME + "_document_update_error";
	public static final String ACTION_ON_UPDATE_DONE = NAME + "_udpate_done";

	// incoming extras
	public static final String EXTRA_CURRENT_USER = "current_user";

	// outgoing extras
	public static final String EXTRA_UPDATED_DOCUMENT_WEB_ID = "document_web_id";
	public static final String EXTRA_UPDATED_DOCUMENT_FORM_WEB_ID = "form_web_id";
	// public static final String EXTRA_UPDATED_DOCUMENT = "updated_document";
	public static final String EXTRA_DOCUMENT_UPDATE_ERROR = "document_update_error";
	public static final String EXTRA_DOCUMENT_STUB = "document_stub";

	// }}

	private User currentUser;

	// Dependencies
	private DocumentManager documentManager;

	private UserAccountsDAO userAccountsDAO;
	private FormsDAO formsDAO;

	public DocumentsUpdateService() {
		super(TAG);
	}

	@Override
	protected void onHandleIntent(Intent intent) {

		// initialize parameters

		Bundle extras = intent.getExtras();

		currentUser = (User) extras.getSerializable(EXTRA_CURRENT_USER);

		String server = currentUser.getAccountServer();
		String userCompanyId = currentUser.getCompanyId();
		String userId = Integer.toString(currentUser.getId());

		FLLogger.d(TAG, "DocumentsUpdateService started on " + server);

		// notify
		Bundle notificationExtras = new Bundle();
		notificationExtras.putSerializable(MainActivity.EXTRA_LOGGED_IN_USER, currentUser);

		ServiceEventNotifierUtility.getDefaultInstance().displayNotificationWithProgress(this,
		        ServiceEventNotifierUtility.NOTIFICATION_ID_PRIMARY, "Syncing documents", 0, 0,
		        true, notificationExtras);

		// service start

		broadcastServiceStarted();
		initializeManagers(server);

		formsDAO.open();
		List<FormStub> formStubs = formsDAO.getAllCompanyFormStubs(server, userCompanyId);
		formsDAO.close();

		GetDocumentUpdatesResult result = documentManager.getDocumentUpdates(formStubs);

		if (result.exceptionList != null) {
			// TODO: notify the user about errors here
		}

		List<DocumentStub> documentUpdateStubs = result.documentsToUpdate;
		List<Document> updatedDocuments = new ArrayList<Document>();

		// String lastUpdatedDocumentDateUpdated = null;

		int documentsToUpdateSize = documentUpdateStubs.size();
		int currentDocumentCount = 0;

		FLLogger.d(TAG, "There are " + documentsToUpdateSize + " documents to update.");

		for (DocumentStub documentStub : documentUpdateStubs) {

			currentDocumentCount++;

			FLLogger.d(TAG, "Updating " + documentStub.toString() + "(" + currentDocumentCount
			        + "/" + documentsToUpdateSize + ")");

			Document updatedDocument = null;
			try {
				updatedDocument = documentManager.updateOrSaveDocument(documentStub, userId);
				// lastUpdatedDocumentDateUpdated = updatedDocument.getDateUpdated();
			} catch (DocumentDownloadFailedException | DocumentUpdateFailedException e) {
				FLLogger.d(TAG, e.getMessage());
				broadcastDocumentUpdateError(documentStub, e);

				// TODO: add recording of failed download here so that the document will
				// be updated next time and the UI may be updated
			}

			if (updatedDocument != null) {

				int authorWebId = Integer.parseInt(updatedDocument.getRequestorWebId());

				userAccountsDAO.open();
				User documentAuthor = userAccountsDAO
				        .getUserWithWebIdAndServer(authorWebId, server);

				// save the user if it's not existing yet
				if (documentAuthor == null) {
					User userFromRequest;
					try {
						userFromRequest = User.createFromDocument(
						        updatedDocument.getDocumentRawJSON(), server);
						documentAuthor = userAccountsDAO.saveOrUpdateUser(userFromRequest);

						FLLogger.d(TAG, "Registered new user: " + userFromRequest.getDisplayName());
					} catch (UserParseException | IncompleteUserValuesException e) {
						e.printStackTrace();
					}
				}
				userAccountsDAO.close();

				String documentAuthorName = "not found";
				if (documentAuthor != null) {
					documentAuthorName = documentAuthor.getDisplayName();
					updatedDocuments.add(updatedDocument);
					broadcastUpdatedDocument(updatedDocument);
				}

				FLLogger.d(TAG, "Updated document " + documentStub.toString() + " Author: "
				        + documentAuthorName);

			}

		}

		ServiceEventNotifierUtility.getDefaultInstance().dismissPrimaryNotification(this);
		boradcastServiceDone();

	}

	private void initializeManagers(String server) {

		// communicator modules
		HttpCommunicator communicator = new HttpCommunicator();

		DocumentCommunicatorModule documentCommModule = new DocumentCommunicatorModule(
		        communicator, server);

		// data access objects
		DocumentsDAO documentsDAO = new DocumentsDAO(getApplicationContext());
		OutgoingDocumentsDAO outgoingDocumentsDAO = new OutgoingDocumentsDAO(
		        getApplicationContext());
		OutgoingStarMarksDAO outgoingStarMarksDAO = new OutgoingStarMarksDAO(
		        getApplicationContext());

		formsDAO = new FormsDAO(getApplicationContext());
		userAccountsDAO = new UserAccountsDAO(getApplicationContext());

		// managers
		documentManager = new DocumentManager(formsDAO, documentsDAO, outgoingDocumentsDAO,
		        outgoingStarMarksDAO, documentCommModule);

	}

	// ========================================================================
	// {{ Broadcaster Methods

	private void broadcastServiceStarted() {
		Intent broadcastIntent = new Intent();
		broadcastIntent.setAction(ACTION_ON_UPDATE_STARTED);
		broadcastIntent.addCategory(Intent.CATEGORY_DEFAULT);
		sendBroadcast(broadcastIntent);
	}

	private void broadcastUpdatedDocument(Document updatedDocument) {

		Intent broadcastIntent = new Intent();
		broadcastIntent.setAction(ACTION_ON_DOCUMENT_UPDATED);
		broadcastIntent.addCategory(Intent.CATEGORY_DEFAULT);

		broadcastIntent
		        .putExtra(EXTRA_UPDATED_DOCUMENT_FORM_WEB_ID, updatedDocument.getFormWebId());
		broadcastIntent.putExtra(EXTRA_UPDATED_DOCUMENT_WEB_ID, updatedDocument.getWebId());

		sendBroadcast(broadcastIntent);
	}

	private void broadcastDocumentUpdateError(DocumentStub documentStub, Exception ex) {
		Intent broadcastIntent = new Intent();
		broadcastIntent.setAction(ACTION_ON_DOCUMENT_UPDATE_ERROR);
		broadcastIntent.addCategory(Intent.CATEGORY_DEFAULT);

		broadcastIntent.putExtra(EXTRA_DOCUMENT_STUB, documentStub);
		broadcastIntent.putExtra(EXTRA_DOCUMENT_UPDATE_ERROR, ex);

		sendBroadcast(broadcastIntent);
	}

	private void boradcastServiceDone() {
		Intent broadcastIntent = new Intent();
		broadcastIntent.setAction(ACTION_ON_UPDATE_DONE);
		broadcastIntent.addCategory(Intent.CATEGORY_DEFAULT);
		sendBroadcast(broadcastIntent);
	}

}
